
use std::os::raw::c_char;
use log::{info};
use ini::Ini;
use nalgebra;
use nalgebra::Vector2;
use nalgebra::Vector3;

use crate::mem_helper;
use crate::cam_config;
use crate::hacker;

// const plugin name
const PLUGIN_NAME: &str = "RBRFluentChaseCam";

#[repr(C)]
pub struct ChaseCamPlugin {
    // pub game: *mut dyn crate::iplugin::IRBRGame,
    _mem_helper: mem_helper::MemHelper,
    _default_cam_config: cam_config::CamConfig,
    _detect_interval: f32,
    _fluent_factor: f32,
    _angle2x: f32,
    _acc2z: f32,
    _spd2z: f32,
    _current_cam_config: cam_config::CamConfig,
    _current_car_direction: nalgebra::Quaternion<f32>,
    _game_cam_config: cam_config::CamConfig,
    _current_game_time: f32,
    _current_car_speed: f32,
    _elapsed_time: f32,

    _delta_x: f32,
    _delta_z: f32,

    _target_pos_z: f32,
    _target_pos_x: f32,
}
#[allow(non_snake_case, dead_code)]
impl ChaseCamPlugin {
    pub fn new() -> Self {
        // ChaseCamPlugin { game }
        ChaseCamPlugin { 
            _mem_helper: mem_helper::MemHelper::new(),
            _default_cam_config: cam_config::CamConfig::new(),
            _detect_interval: 0.25,
            _fluent_factor: 0.5,
            _angle2x: 0.1,
            _acc2z: 0.01,
            _spd2z: 0.1,
            _current_cam_config: cam_config::CamConfig::new(),
            _current_car_direction: nalgebra::Quaternion::new(0.0, 0.0, 0.0, 0.0),
            _game_cam_config: cam_config::CamConfig::new(),
            _current_game_time: 0.0,
            _current_car_speed: 0.0,
            _elapsed_time: 0.0,

            _delta_x: 0.0,
            _delta_z: 0.0,

            _target_pos_z: 0.0,
            _target_pos_x: 0.0,
        }
    }

    pub fn get_name(&mut self) -> *const c_char {
        format!("{PLUGIN_NAME}\0").as_ptr() as *const c_char
    }

    pub fn init(&mut self) {
        // load camera configuration
        info!("Init ChaseCamPlugin");
        self.load_camera_config();
        info!("ChaseCamPlugin initialized.");
        // start loop to read game memory
        // self.start_thread();
    }

    pub fn load_camera_config(&mut self) {
        // load camera configuration
        if let Some(game_path) = std::env::current_exe().unwrap().parent() {
            let conf_path = game_path.join("Plugins").join(PLUGIN_NAME).join(format!("{PLUGIN_NAME}.ini"));
            let conf = Ini::load_from_file(conf_path).unwrap();
            let cam_section = conf.section(Some("Cam")).unwrap();
            self._default_cam_config.load_from_ini(&cam_section);
            info!("Loaded camera configuration. {:?}", self._default_cam_config);

            let misc_section = conf.section(Some("Misc")).unwrap();
            self._detect_interval = misc_section.get("Detect_Interval").unwrap().parse::<f32>().unwrap();
            self._fluent_factor = misc_section.get("Fluent_Factor").unwrap().parse::<f32>().unwrap();
            self._angle2x = misc_section.get("Angle2X").unwrap().parse::<f32>().unwrap();
            self._acc2z = misc_section.get("Acc2Z").unwrap().parse::<f32>().unwrap();
            self._spd2z = misc_section.get("Spd2Z").unwrap().parse::<f32>().unwrap();
            info!("Loaded misc configuration. Detect_Interval: {}, Fluent_Factor: {}, Angle2X: {}", self._detect_interval, self._fluent_factor, self._angle2x);
        }
    }

    pub fn on_game_mode_changed(&mut self) {
        // game mode changed
    }
    
    pub fn on_end_frame(&mut self) {
        // calculate delta time
        let race_time = self.get_race_time();
        let mut elpased_time = race_time - self._current_game_time;
        if elpased_time < 0.0 {
            elpased_time = 0.0
        }
        self._current_game_time = race_time;
        self._elapsed_time += elpased_time;
        self.read_from_memory(elpased_time);
    }

    pub fn read_from_memory(&mut self, elapsed_time: f32) {
        // read from memory
        let game_mode: i32 = unsafe {
            hacker::RBR_ReadGameMode()
        };

        if game_mode != 0x01 && game_mode != 0x08 {
            // not driving/replaying
            return;
        }

        if !self._mem_helper.open_process() {
            info!("Failed to open process.");
            return;
        }

        self._current_cam_config.read_from_memory(&mut self._mem_helper);
        // info!("Current camera configuration: {:?}", self._current_cam_config);

        if self._current_cam_config.cam_type != 0x03 {
            // not chase cam
            return;
        }

        let speed = self.get_race_speed();
        let delta_speed = speed - self._current_car_speed;
        self._current_car_speed = speed;
        let mut acceleration = 0.0;
        if elapsed_time != 0.0 {
            acceleration = delta_speed / elapsed_time;  // we don't care about acceleration
        }

        if self._elapsed_time < self._detect_interval {
            // update car pos towards
            self.change_camera_position(self._delta_x * elapsed_time / self._detect_interval, self._delta_z * elapsed_time / self._detect_interval, elapsed_time);
            return;
        }
        info!("Total Elapsed time: {}, Elapsed time: {}, Detect Interval: {}", self._elapsed_time, elapsed_time, self._detect_interval);
        self._elapsed_time = 0.0;

        self._delta_x = self.get_car_direction_delta_x();
        self._target_pos_x = self._default_cam_config.Pos_x + self._delta_x;
        self._delta_x = self._target_pos_x - self._current_cam_config.Pos_x;

        self._delta_z = self.get_delta_z(speed);
        self._target_pos_z = self._default_cam_config.Pos_z + self._delta_z;
        self._delta_z = self._target_pos_z - self._current_cam_config.Pos_z;

        // should not miss this update
        self.change_camera_position(self._delta_x * elapsed_time / self._detect_interval, self._delta_z * elapsed_time / self._detect_interval, elapsed_time);
    }

    pub fn start_thread(&mut self) {
        info!("Thread started.");
    }

    pub fn get_race_time(&mut self) -> f32 {
        let base_addr = self._mem_helper.read_pointer(0x165FC68);
        let race_time = self._mem_helper.read_float(base_addr + 0x140);
        race_time
    }

    pub fn get_race_speed(&mut self) -> f32 {
        let base_addr = self._mem_helper.read_pointer(0x165FC68);
        let speed = self._mem_helper.read_float(base_addr + 12);
        speed
    }

    pub fn get_rotate_angle(&mut self, to: Vector2<f32>, from: Vector2<f32>) -> f32 {
        let dot = to.dot(&from);
        let det = to.x * from.y - to.y * from.x;
        let angle = det.atan2(dot);
        angle
    }

    pub fn get_car_direction_delta_x(&mut self) -> f32 {
        // read car direction
        let car_movement_pointer = self._mem_helper.read_pointer(0x008EF660);
        if car_movement_pointer == 0 {
            info!("Car movement pointer is null.");
            return 0.0;
        }
        
        let car_direction_x = self._mem_helper.read_float(car_movement_pointer + 0x100);
        let car_direction_y = self._mem_helper.read_float(car_movement_pointer + 0x104);
        let car_direction_z = self._mem_helper.read_float(car_movement_pointer + 0x108);
        let car_direction_w = self._mem_helper.read_float(car_movement_pointer + 0x10C);

        let car_direction = nalgebra::Quaternion::new(car_direction_w, car_direction_x, car_direction_y, car_direction_z);

        let inversed = self._current_car_direction.try_inverse();
        if inversed.is_none() {
            self._current_car_direction = car_direction;
            return 0.0;
        }
        let car_direction_delta = car_direction * inversed.unwrap();

        info!("last car direction: {:?}", self._current_car_direction);
        info!("Car direction: {:?}", car_direction);
        info!("Car direction delta: {:?}", car_direction_delta);

        // let mut angle = 2.0 * car_direction_delta.w.acos();
        // let norm = car_direction_delta.i.powi(2) + car_direction_delta.j.powi(2) + car_direction_delta.k.powi(2);
        // let axis = if norm > 1e-6 {
        //     Vector3::new(car_direction_delta.i / norm, car_direction_delta.j / norm, car_direction_delta.k / norm)
        // } else {
        //     Vector3::new(0.0, 1.0, 0.0)
        // };

        // angle = if axis.y > 0.0 {
        //     angle
        // } else {
        //     -angle
        // };
        // info!("Axis: {:?}", axis);
        // info!("angle: {}", angle);

        // if axis.y != 0.0 && !angle.is_nan() && angle.abs() > 0.020 {    // 
        //     info!("Car direction: x: {}, y: {}, z: {}", car_direction.i, car_direction.j, car_direction.k);
        //     info!("Car direction before: x: {}, y: {}, z: {}", self._current_car_direction.i, self._current_car_direction.j, self._current_car_direction.k);
        //     info!("Angle: {}", angle);
        // } else {
        //     angle = 0.0;
        // }

        let (w, x, y, z) = (car_direction_delta.w, car_direction_delta.i, car_direction_delta.j, car_direction_delta.k);
        let angle = (2.0 * (w * y + x * z)).atan2(1.0 - 2.0 * (y * y + z * z));

        let delta_x = angle * self._angle2x;

        // update current car direction
        self._current_car_direction = car_direction;
        info!("Delta x: {}", delta_x);
        delta_x
    }

    fn get_delta_z(&mut self, spd: f32) -> f32 {
        // delta z is calculated by the acceleration of the car
        let delta_z = spd.abs() * self._spd2z;
        info!("Delta z: {}", delta_z);
        delta_z
    }

    fn change_camera_position(&mut self, delta_x: f32, delta_z: f32, elapsed_time: f32) {
        // change camera position x 
        // delta_x and delta_z should be calculated by delta in every detection interval * elapsed time / detect interval
        // so delta_x and delta_z are not time dependent, or they are dependent on detection interval which is constant
        self._current_cam_config.Pos_x += delta_x;
        self._current_cam_config.Pos_z += delta_z;

        let delta_x_to_default = self._default_cam_config.Pos_x - self._current_cam_config.Pos_x;
        let delta_y_to_default = self._default_cam_config.Pos_y - self._current_cam_config.Pos_y;
        let delta_z_to_default = self._default_cam_config.Pos_z - self._current_cam_config.Pos_z;

        // let delta = nalgebra::Vector3::new(delta_x_to_default, delta_y_to_default, delta_z_to_default);

        // if delta.norm() > 0.0 {
        //     info!("Delta: {:?}", delta);
        // }

        // if delta.norm() < 0.001 {
        //     // only fluent x
        //     self._current_cam_config.Pos_x = self._default_cam_config.Pos_x;
        // } else {
        //     self._current_cam_config.Pos_x += delta_x_to_default * self._fluent_factor * elapsed_time;
        // }

        self._current_cam_config.write_to_memory(&mut self._mem_helper);
    }
}

unsafe impl Send for ChaseCamPlugin {}

